package terra.model.map;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

import terra.Config;
import terra.managers.MapManager;
import terra.managers.ThreadPoolManager;
import terra.model.Weather;
import terra.model.World;
import terra.model.object.GameObject;
import terra.model.object.Player;
import terra.model.object.map.AbstractMapObject;
import terra.model.object.map.GroundObject;
import terra.model.object.map.MapObject;
import terra.network.Connections;
import terra.network.packet.Packet;
import terra.network.packet.server.ChunkInfo;
import terra.network.packet.server.WeatherInfo;

/**
 * Карта. В ней находится массив блоков, и индекс карты
 */
public class Map {

	public static class MapContainer implements Serializable {
		private static final long serialVersionUID = 1L;
		public AbstractMapObject[][] map = new AbstractMapObject[Config.mapWidth][Config.mapHeight];
		public ArrayList<MapObject> water = new ArrayList<MapObject>();
		public int[] topGroundLine = new int[Config.mapWidth];
		public int meterLeft;
		public int meterRight;
		int biomId = -1;
	}

	public MapContainer container = new MapContainer();
	public ConcurrentHashMap<Integer, GameObject> objects = new ConcurrentHashMap<Integer, GameObject>();
	ConcurrentHashMap<Integer, Player> players = new ConcurrentHashMap<Integer, Player>();
	public int index;
	public boolean active = false;
	Object activator;
	public Weather weather;
	public long lastSaved = 0;
	ScheduledFuture<?> saveTask;

	public ConcurrentHashMap<Integer, GameObject> getObjects(){
		return objects;
	}
	public int getBiomId() {
		return container.biomId;
	}

	public void setBiomId(int id) {
		container.biomId = id;
	}

	public int[] getGroundLine() {
		return container.topGroundLine;
	}

//	public void setGroundLine(int[] map) {
//		container.topGroundLine = map;
//	}

	public AbstractMapObject[][] getMap() {
		return container.map;
	}

	public void setMap(AbstractMapObject[][] map) {
		container.map = map;
	}

	public ArrayList<MapObject> getWater() {
		return container.water;
	}

	public void setWater(ArrayList<MapObject> water) {
		container.water = water;
	}

	public void addObject(GameObject object) {
		if (!getObjects().containsKey(object.getObjectId())) {
			getObjects().put(object.getObjectId(), object);
			onEnter(object);
		}
	}

	public void removeObject(GameObject object) {
		if (getObjects().containsKey(object.getObjectId())) {
			getObjects().remove(object.getObjectId());
			onExit(object);
		}
	}

	private void onEnter(GameObject object) {
		if (object instanceof Player) {
			players.put(object.getObjectId(), (Player) object);
			sendWeatherInfo((Player) object);
			World.getInstance().addUpdateObject(object);
			System.out.println("onEnter map " + index + " player " + object);
			activate();
			activator = object;
			if (players.size() != 0) {
				if (getLeftMap() != null && !getLeftMap().active)
					getLeftMap().activate();
				if (getRightMap() != null && !getRightMap().active)
					getRightMap().activate();
			}
		}
	}

	private void onExit(GameObject object) {
		if (object instanceof Player) {
			players.remove(object.getObjectId());

			boolean playerExists = false;
			for (int i = 0; i < getObjects().size(); i++) {
				if (getObjects().get(i) instanceof Player) {
					playerExists = true;
				}
			}
			System.out.println("onExit map " + index + " player " + object);
			if (!playerExists) {
				if (!neighborsContainsPlayers())
					deactivate();
				if (getLeftMap() != null
						&& !getLeftMap().neighborsContainsPlayers())
					getLeftMap().deactivate();
				if (getRightMap() != null
						&& !getRightMap().neighborsContainsPlayers())
					getRightMap().deactivate();
			}
		}
	}

	public void sendWeatherInfo(Player p) {
		if (weather == null)
			return;
		WeatherInfo info = new WeatherInfo();
		info.id = weather.def.id;
		info.intensity = weather.intensity;
		info.light = weather.lightIntense;
		if (p != null)
			p.send(info);
		else
			send(info, false);
	}

	public void activate() {
		if (active)
			return;
		active = true;
		for (GameObject object : getObjects().values()) {
			World.getInstance().addUpdateObject(object);
		}
		saveTask = ThreadPoolManager.getInstance().scheduleGeneral(new Runnable(){

			@Override
			public void run() {
				save();
			}}, Config.mapSaveDelay);
		
		System.out.println("Map " + index + " activated");
		if (MapManager.getInstance().minMapIndex == index) {
			if (!MapManager.getInstance().getWaitingMaps()
					.contains(MapManager.getInstance().minMapIndex - 1)) {
				ThreadPoolManager
						.getInstance()
						.executeCreate(
								new MapCreater(
										MapManager.getInstance().minMapIndex - 1));
			}
		} else if (MapManager.getInstance().maxMapIndex == index) {
			if (!MapManager.getInstance().getWaitingMaps()
					.contains(MapManager.getInstance().maxMapIndex + 1)) {
				ThreadPoolManager
						.getInstance()
						.executeCreate(
								new MapCreater(
										MapManager.getInstance().maxMapIndex + 1));
			}
		}
	}

	public void deactivate() {
		if (!active)
			return;
		for (int i = 0; i < getObjects().size(); i++) {
			GameObject obj = getObjects().get(i);
			if (obj != null)
				World.getInstance().getUpdateObjects().remove(obj);
		}
		active = false;
		if (MapManager.getInstance().init){	save(); }
//		saveTask.
//		saveTask.cancel(false);
//		saveTask = null;
		System.out.println("Map " + index + " deactivated");
	}

	public Map getLeftMap() {
		return index == MapManager.getInstance().minMapIndex ? null
				: MapManager.getInstance().getMap(index - 1);
	}

	public Map getRightMap() {
		return index == MapManager.getInstance().maxMapIndex ? null
				: MapManager.getInstance().getMap(index + 1);
	}

	boolean neighborsContainsPlayers() {
		if (getLeftMap() != null && getLeftMap().players.size() != 0)
			return true;
		if (getRightMap() != null && getRightMap().players.size() != 0)
			return true;
		if (players.size() != 0)
			return true;
		return false;
	}

	public void send(Packet packet, boolean toNeighborsToo) {
		for (Player p : players.values()) {
			p.send(packet);
		}
		if (toNeighborsToo) {
			if (index > MapManager.getInstance().minMapIndex) {
				MapManager.getInstance().getMap(index - 1).send(packet, false);
			}
			if (index < MapManager.getInstance().maxMapIndex) {
				MapManager.getInstance().getMap(index + 1).send(packet, false);
			}
		}
	}

	public void sendMap(Player p, boolean all) {
		for (int xx = 0; xx < Config.mapWidth; xx += ChunkInfo.size) {
			for (int yy = 0; yy < Config.mapHeight; yy += ChunkInfo.size) {

				int chunkArrayLength = 0;
				for (int x = xx; x < xx + ChunkInfo.size; x++) {
					for (int y = yy; y < yy + ChunkInfo.size; y++) {
						if (getMap()[x][y] != null) {
							chunkArrayLength += 1;
						}
					}
				}
				if (chunkArrayLength == 0)
					continue;
				ChunkInfo chunk = new ChunkInfo(xx + index * Config.mapWidth,
						yy);
				chunk.init(chunkArrayLength);
				int i = 0;
				for (int x = xx; x < xx + ChunkInfo.size; x++) {
					for (int y = yy; y < yy + ChunkInfo.size; y++) {

						if (getMap()[x][y] == null) {
							continue;
						}
						chunk.coords[i] = (short) ((x - xx) + (y - yy)
								* ChunkInfo.size);
						chunk.ids[i] = getMap()[x][y].getId();
						chunk.types[i] = getMap()[x][y].getType();
						if (getMap()[x][y] instanceof GroundObject)
							chunk.hps[i] = (short) ((GroundObject) getMap()[x][y])
									.getHp();
						else
							chunk.masses[i] = ((MapObject) getMap()[x][y])
									.getMass();
						i++;
					}
				}
				if (all) {
					Connections.broadcast(chunk);
				} else {
					p.getHandler().send(chunk);
				}
				try {
					Thread.sleep(25);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

	public void finalize() {
		container.water = null;
		objects = null;
		container.topGroundLine = null;
		container = null;
	}

	List<MapObject> changedWater = new ArrayList<MapObject>();

	public void sendWater() {
		// changedWater.clear();
		// for(int j = 0; j < water.size(); j++) {
		// MapObject obj = water.get(j);
		// if (obj == null){
		// water.remove(j);
		// continue;
		// }
		// if (!obj.massChanged())
		// continue;
		// changedWater.add(obj);
		// }
		//
		// if (changedWater.size() == 0)
		// return;
		// WaterInfo wi = new WaterInfo();
		// wi.init(changedWater.size());
		// for(int j = 0; j < changedWater.size(); j++) {
		// MapObject obj = changedWater.get(j);
		// wi.coordsx[j] = obj.getX()/Config.tileSize;
		// wi.coordsy[j] = obj.getY()/Config.tileSize;
		// wi.masses[j] = obj.getMass();
		// //obj.setOldMass(obj.getMass());
		// }
		// this.send(wi, true);

		for (int j = 0; j < getWater().size(); j++) {
			MapObject obj = getWater().get(j);
			if (obj == null) {
				getWater().remove(obj);
				return;
			}
			if (obj.massChanged()) {
				obj.sendTileInfo();
			}
		}
	}

	public void setWeather(Weather w, boolean b) {
		weather = w;
		if (b)
			sendWeatherInfo(null);
	}

	public void save() {
		// if (lastSaved != 0 && lastSaved+15*60*1000 <
		// System.currentTimeMillis())
		// return;
		ThreadPoolManager.getInstance().executeSave(new MapSaver(this, false));
		lastSaved = System.currentTimeMillis();
	}

	public void load() {
		ThreadPoolManager.getInstance().executeLoad(new MapLoader(this));
	}

}
